#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using BitwiseOre.IO;
using BitwiseOre.SharedMemory;
using NUnit.Framework;

namespace BitwiseOre.Test.SharedMemory.Integration
{
    [TestFixture]
    public sealed class SharedMemoryFactoryFixture : AssertionHelper
    {
        private SharedMemoryContext _context;

        [SetUp]
        public void SetUp()
        {
            _context = new SharedMemoryContext();
        }

        [Test(Description = "Creating a factory with a null contect throws an exception")]
        public void Create_WithNullContext()
        {
            Expect(() => SharedMemoryFactory.Create(null, "name"),
                   Throws.InstanceOf<ArgumentNullException>());
        }

        [Test(Description = "Creating a factory with null name throws an excpetion")]
        public void Create_WithNullName()
        {
            Expect(() => SharedMemoryFactory.Create(_context, null),
                   Throws.InstanceOf<ArgumentNullException>());
        }

        [Test(Description = "Creating a factory with a name that does not exist throws and expctions")]
        public void Create_WithNonExistantname()
        {
            Expect(() => SharedMemoryFactory.Create(_context, "NonExistantName"),
                    Throws.InstanceOf<ListenerNotFoundException>());
        }

        [Test(Description = "Connecting after a factory has been disposed throws an exception.")]
        public void Connect_AfterDispose()
        {
            const string name = "TestListnerA";

            using (SharedMemoryListener.Create(_context, name))
            {
                var factory = SharedMemoryFactory.Create(_context, name);
                factory.Dispose();
                Expect(() => factory.Connect(), Throws.InstanceOf<ObjectDisposedException>());
            }
        }

        [Test(Description = "Connecting timesout if not accepted")]
        public void Connect_Timesout()
        {
            const int timeout = 1000;
            var stopwatch = new Stopwatch();
            const string name = "TestListenerB";
            var timedout = false;

            using (SharedMemoryListener.Create(_context, name))
            using (var factory = SharedMemoryFactory.Create(_context, name))
            {
                factory.ConnectTimeout = timeout;
                stopwatch.Start();
                try
                {
                    factory.Connect();
                }
                catch (TimeoutException)
                {
                    timedout = true;
                }
                stopwatch.Stop();
            }

            Expect(timedout, "Connect did not time out.");
            Expect(stopwatch.ElapsedMilliseconds, EqualTo(timeout).Within(75));
        }

        [Test(Description = "Connecting establishes a connection to a listener that is accepting.")]
        public void Connect_ConnectsToListener()
        {
            const string name = "TestListenerC";

            DisconnectableStream result;
            using (var listener = SharedMemoryListener.Create(_context, name))
            using (var factory = SharedMemoryFactory.Create(_context, name))
            {
                var barrier = new Barrier(2);

                var asyncresult = listener.BeginAccept(_ => barrier.SignalAndWait(), null);
                result = factory.Connect();
                barrier.SignalAndWait();
                listener.EndAccept(asyncresult).Dispose();
                result.Dispose();
            }

            Expect(result, Not.Null);
        }

        [Sequential,Test(Description = "Trying to establish more connections than the pending connection limit throws " +
                            "an exception.")]
        public void Connect_BeyondPendingConnectionLimit(
            [ValueSource("PendingConnectionsValues")] int pendingConnections)
        {
            var name = string.Format("TestListenerD{0}", pendingConnections);
            _context.ListenerContext.MaxPendingConnections = pendingConnections;

            using (var listener = SharedMemoryListener.Create(_context, name))
            using (var factory = SharedMemoryFactory.Create(_context, name))
            {
                var range = Enumerable.Range(0, pendingConnections);

                var asyncresults = (from _ in range select factory.BeginConnect(null, null)).ToArray();
                var factoryconnections = from asyncresult in asyncresults select factory.EndConnect(asyncresult);
                var listenerconnections = from _ in range select listener.Accept();

                Expect(() => factory.Connect(), Throws.InstanceOf<UnableToConnectException>());

                listenerconnections = listenerconnections.ToArray();
                factoryconnections = factoryconnections.ToArray();

                foreach (var listenerconnection in listenerconnections)
                {
                    listenerconnection.Dispose();
                }
                foreach (var factoryconnection in factoryconnections)
                {
                    factoryconnection.Dispose();
                }
            }
        }

        public static int[] PendingConnectionsValues = new[] {1, 2, 5, 15, 16, 17, 127, 128};
    }

}